﻿using System;
using Tao.OpenGl;
using System.Collections.Generic;

namespace Aeon.Framework.Objects.Models.Model3DS
{

/// <summary>
/// Transfer object class with model info for 3DS files.
/// 
/// </summary>
public sealed class Data3DS
    {
    // --- Fields:

    private Boolean normalized;                 // Already normalized flag.
    private Int32 list;                         // Gl list identifier.

    private Vertex[] normals;                   // Triangle normals.

    private Vertex[] vertices;                  // Triangle coordinates.
    private Vertex[] indices;                   // Triangle indexes.
    private Vertex[] texels;                    // Texture coordinates.

    private List<MaterialFaces> materials;      // Materials list.

    // --- Properties:

    public List<MaterialFaces> Materials { get { return(materials); } }

    public Vertex[] Vertices { get { return(vertices); } set { vertices = value; } }
    public Vertex[] Indices { get { return(indices); } set { indices = value; } }
    public Vertex[] Texels { get { return(texels); } set { texels = value; } }

    // --- Constructor:

    public Data3DS()
        {
        list = -1;
        normalized = false;

        materials = new List<MaterialFaces>();
        }
    
    // --- Public Methods:

    public void Draw()
        {
        if (indices == null) return;
			
        // use a GlList for performance
        if (list >= 0)
            {
            Gl.glCallList(list);
            return;
            }
		
        else { 
             // First run we create the glList and manually render our entity.
             // Fails between Begin and End?
             // if GL.INVALID_VALUE ...
             list = Gl.glGenLists(1);       

             Gl.glNewList(list, Gl.GL_COMPILE);

             // some 3ds files have no material faces, so we just draw all the faces
             bool noMaterials = (materials.Count == 0) ? (true):(false);
             if (noMaterials)
                {
                MaterialFaces m = new MaterialFaces();
                m.Material = AeonMaterials.Default;
                materials.Add(m);
                }
        		
             foreach(MaterialFaces m in materials)
                {
                AeonMaterial material = m.Material;
        		
                // set up light properties
                material.Apply();
    	        
                // if we have a texture, bind it                
                material.Texture.Bind();

                // Draw every triangle in the entity, it's normal, and it's material (if exists)
                Gl.glBegin (Gl.GL_TRIANGLES);		
    				
                for(int ii = 0; ii < (noMaterials ? indices.Length : m.Faces.Length); ii++)
                    { 
                    Vertex tri = (noMaterials) ? (indices[ii]):(indices[m.Faces[ii]]);
    				
                    // Vertex 1
                    if (normalized) Gl.glNormal3d(normals[(int)tri.X].X, normals[(int)tri.X].Y, normals[(int)tri.X].Z );
                    if (!material.Texture.Core.Empty) Gl.glTexCoord2f(texels[(int)tri.X].U, texels[(int)tri.X].V);
                    Gl.glVertex3d(vertices[(int)tri.X].X, vertices[(int)tri.X].Y, vertices[(int)tri.X].Z );

                    // Vertex 2
                    if (normalized) Gl.glNormal3d(normals[(int)tri.Y].X, normals[(int)tri.Y].Y, normals[(int)tri.Y].Z );
                    if (!material.Texture.Core.Empty) Gl.glTexCoord2f(texels[(int)tri.Y].U, texels[(int)tri.Y].V);
                    Gl.glVertex3d(vertices[(int)tri.Y].X, vertices[(int)tri.Y].Y, vertices[(int)tri.Y].Z);

                    // Vertex 3
                    if (normalized) Gl.glNormal3d(normals[(int)tri.Z].X, normals[(int)tri.Z].Y, normals[(int)tri.Z].Z );
                    if (!material.Texture.Core.Empty) Gl.glTexCoord2f(texels[(int)tri.Z].U, texels[(int)tri.Z].V);
                    Gl.glVertex3d(vertices[(int)tri.Z].X, vertices[(int)tri.Z].Y, vertices[(int)tri.Z].Z );
                    }

                Gl.glEnd();
                }
        			
             Gl.glEndList();
             }
        }

    /// <summary>
    /// Calculates all triangle normals.
    /// 
    /// </summary>
    public bool CalculateNormals()
        {
        if (indices != null && vertices != null && !normalized)
            {
            normals = new Vertex[vertices.Length];
            Vertex[] temps = new Vertex[indices.Length];

            for (int ii=0; ii < indices.Length; ii++)
                {
                Vertex tri = indices[ii];
				
                Vertex v1 = new Vertex();
                v1.X = vertices[(int)tri.X].X - vertices[(int)tri.Y].X;
                v1.Y = vertices[(int)tri.X].Y - vertices[(int)tri.Y].Y;
                v1.Z = vertices[(int)tri.X].Z - vertices[(int)tri.Y].Z;

                Vertex v2 = new Vertex();
                v2.X = vertices[(int)tri.Y].X - vertices[(int)tri.Z].X;
                v2.Y = vertices[(int)tri.Y].X - vertices[(int)tri.Z].Y;
                v2.Z = vertices[(int)tri.Y].X - vertices[(int)tri.Z].Z;
                
                Vertex crossProduct = new Vertex();
                crossProduct.X = v1.Y*v2.Z - v1.Z*v2.Y;
                crossProduct.Y = v1.Z*v2.X - v1.X*v2.Z;
                crossProduct.Z = v1.X*v2.Y - v1.Y*v2.X;

                temps[ii] = crossProduct;
                }
			
            for (int ii = 0; ii < vertices.Length; ii++)
                {
                Vertex v = new Vertex();
				
                for (int jj = 0; jj < indices.Length; jj++)
                    {
                    Vertex tri = indices[jj];
					
                    if (tri.X == ii || tri.Y == ii || tri.Z == ii)
                        {
                        v.X += temps[jj].X;
                        v.Y += temps[jj].Y;
                        v.Z += temps[jj].Z;
                        }
                    }

                float magnitude = (float) Math.Pow((Math.Pow(v.X,2))+(Math.Pow(v.Y,2))+(Math.Pow(v.Z,2)),1/2);
                v.X = v.X/magnitude;
                v.Y = v.Y/magnitude;
                v.Z = v.Z/magnitude;

                normals[ii] = v;
                }

            normalized = true;
            return(true);
            }

        else return(false);
        }

    }

/// <summary>
/// [TODO] Remove this...
/// 
/// </summary>
public sealed class MaterialFaces
    {
	public AeonMaterial Material;
	public UInt16[] Faces;
	}


}
